/**
 * MeetingAgent class used to hold names addresses and times of meeting
 * along with which times suit which contacts.
 * Once the agent has been set, the toString and fromString methods 
 * are used to send the agent, and recreate the agent once received.
 * @ author Francis, Aidan and Jonathan
 */

package agent;
import java.util.Vector;
import ap.Tokenizer;
import ap.NoSuchElementException;

public class MeetingAgent implements Agent{

	private String agentType;
	private Vector contacts;    
	private Vector addresses;
	private Vector startTimes;
	private Vector finishTimes;
	private boolean[][] available;	//[contacts][times]
	private int nextAddressIndex;

	/***************************************************
	 * chars used to read from and write to delimited 
	 */
	private static final char FIELD_DELIM     = '|';
	private static final char VALUE_DELIM     = '=';
	
	private static final char FIELD_NAME      = 'N';
	private static final char FIELD_ADDRESS   = 'A';
	private static final char FIELD_INDEX     = 'X';
	
	private static final char FIELD_START     = 'S';
	private static final char FIELD_FINISH    = 'F';
	
	private static final char ELEMENT_BOOL    = 'B';
	/***************************************************
	 * constructor sets up vectors, agent type and nextAddressIndex
	 * attributed are entered either by UI or from string method
	 */
	public MeetingAgent(){
		agentType = Agent.TYPE_MEETING;
		contacts = new Vector();
		addresses = new Vector();
		startTimes = new Vector();
		finishTimes = new Vector();	
		nextAddressIndex = -1;//this will be updated to 0 the first time it is sent
	}       
	/**************************************************************************
	 * access methods
	 */
	public String getType(){
		return this.agentType;
	}
	public int sizeOfContacts(){
		return this.contacts.size();
	}
	public int sizeOfAddresses(){
		return this.addresses.size();
	}
	public int sizeOfStartTimes(){
		return this.startTimes.size();
	}
	public int sizeOfFinishTimes(){
		return this.finishTimes.size();
	}	
	public String getContact(int n){
		return this.contacts.elementAt(n).toString();
	}
	public String getAddresses(int n){
		return this.addresses.elementAt(n).toString();
	}
	public String getStartTimes(int n){
		return this.startTimes.elementAt(n).toString();
	}
	public String getFinishTimes(int n){
		return this.finishTimes.elementAt(n).toString();
	}
	public boolean getAvailability(int n, int m){
		return this.available[n][m];
	}
	public int getNextAddressIndex(){
		return this.nextAddressIndex;
	}
	/**************************************************************************
	 * used to update the next destination
	 */
	public void incrementToNextAddress(){ 
		this.nextAddressIndex++;
	}
	/**************************************************************************
	 * methods to update meeting agent data
	 */
	public void addContact(String newContact, String newAddress){
		this.contacts.addElement (newContact);
		this.addresses.addElement(newAddress);
	}
	public void addTime(String newStart, String newFinish){
		this.startTimes.addElement (newStart);
		this.finishTimes.addElement(newFinish);
	}
	public void addAvailableTime(int contactIndex,int timeIndex){
		available[contactIndex][timeIndex] = true;
	}
	/**************************************************************************
	 **************************************************************************
	 * read in all the agents values from a string
	 * the order in which they are read in is important and should 
	 * not be changed without also changing the toString function
	 * 
	 * @param agentString
	 */
	public void fromString( String agentString ){
		int i = 0;
		int j = 0;
		String field, value;
		Tokenizer tokField = new Tokenizer(agentString, FIELD_DELIM);
		
		//first read in agent type
		//try {
		//	this.agentType = tokField.nextToken();
		//} catch (NoSuchElementException e) {
		//	System.err.println("NoSuchElementException caught in MeetingAgent.fromString()");
		//	e.printStackTrace();
		//}
		//then read in all the agent values
		while( tokField.hasMoreTokens() ){			
			try {
				field = tokField.nextToken();
				value = field.substring(2);
				
				if( field.charAt(0) == FIELD_INDEX ){
					this.nextAddressIndex = Integer.parseInt(value);
				}
				else if( field.charAt(0) == FIELD_NAME ){					
					this.contacts.addElement(value);
				}
				else if( field.charAt(0) == FIELD_ADDRESS ){
					this.addresses.addElement(value);
				}
				else if( field.charAt(0) == FIELD_START ){
					this.startTimes.addElement(value);
				}
				else if( field.charAt(0) == FIELD_FINISH ){
					this.finishTimes.addElement(value);
				}
				else if( field.charAt(0) == ELEMENT_BOOL ){
					//if the boolean array has not been created, do so now
					if( available == null )
						this.available = new boolean[contacts.size()][startTimes.size()];
									
					//if statements act as a random access for loop
					//where the i j counters are declared outside the loop
					if( i < contacts.size() ){
						if( j < startTimes.size() ){
							if( value.charAt(0) == 'T' )
								this.available[i][j] = true;
							else
								this.available[i][j] = false;
							
							j++;
						}
						//when the end of times has been reached
						//reset the j counter and move to the next contact
						else{	
							j = 0;
							i++;
							
							if( value.charAt(0) == 'T' )
								this.available[i][j] = true;
							else
								this.available[i][j] = false;
							
							j++;
						}						
					}
				}				
			} catch (NoSuchElementException e) {
				System.err.println("NoSuchElementException cought in MeetingAgent.fromString()");
				e.printStackTrace();
			}
		}
	}
	/**************************************************************************
	 * overwrite the toString method to write out all the agents values 
	 * the order in which they are written out is important and should 
	 * not be changed without also changing the fromString function
	 **/
	public String toString() {
		String agentString = new String();
		
		//if the boolean array has not been created, do so now and set false
		//this is only the case if the agent has just been created and not received
		if( available == null ){
			this.available = new boolean[contacts.size()][startTimes.size()];
			
			for(int i=0; i<this.contacts.size(); i++){
				for(int j=0; j<this.startTimes.size(); j++){
					this.available[i][j] = false;
				}
			}
		}		
		
		//first write out the agent type
		agentString += this.agentType;  	//	MEETING
		agentString += FIELD_DELIM;			//	|

		//then write out the nextAddressIndex
		agentString += FIELD_INDEX;			//	I
		agentString += VALUE_DELIM;			//	=		
		agentString += nextAddressIndex;	//  index
		agentString += FIELD_DELIM;			//	|
		
		//then write out all contact names and addresses in pairs
		for( int i=0; i<this.contacts.size(); i++ ){
			agentString += FIELD_NAME;		//	N
			agentString += VALUE_DELIM;		//	=			
			agentString += this.contacts.elementAt(i);
			agentString += FIELD_DELIM;		//	|
			
			agentString += FIELD_ADDRESS;	//	A
			agentString += VALUE_DELIM;		//	=
			agentString += this.addresses.elementAt(i);
			agentString += FIELD_DELIM;		//	|
		}
		
		//then write out start and finish times in pairs
		for( int i=0; i<this.startTimes.size(); i++ ){
			agentString += FIELD_START;		//	S
			agentString += VALUE_DELIM;		//	=			
			agentString += this.startTimes.elementAt(i);
			agentString += FIELD_DELIM;		//	|
			
			agentString += FIELD_FINISH;	//	F
			agentString += VALUE_DELIM;		//	=
			agentString += this.finishTimes.elementAt(i);
			agentString += FIELD_DELIM;		//	|
		}			
		
		//lastly write out the availability array
		for(int i=0; i<this.contacts.size(); i++){
			for(int j=0; j<this.startTimes.size(); j++){
				agentString += ELEMENT_BOOL;	//	B
				agentString += VALUE_DELIM;		//	=
				if( this.available[i][j] == true )
					agentString += "T";
				else
					agentString += "F";
				agentString += FIELD_DELIM;		//	|
			}
		}

		return agentString;
	}

	/**************************************************************************
	 * return the next address this agent is to be sent to
	 * 
	 * @return valid address without port or empty string if not available
	 **/
	public String getNextAddress(){
		String nextAddress = "";
		if(nextAddressIndex < addresses.size()){
			nextAddress = addresses.elementAt(nextAddressIndex).toString();	
			Tokenizer tok = new Tokenizer(nextAddress, ":");
			try{
				nextAddress = tok.nextToken() + ":" + tok.nextToken() + ":";
			}catch(NoSuchElementException e){
				System.err.println("NoSuchElementException caught in MeetingAgent.getNextAddress()");
				e.printStackTrace();
			}			
		}
		else{
			System.err.println("nextAddressIndex is trying to access an address " +
					"that is not there in MeetingAgent.getNextAddress()");
		}
		return nextAddress;
	}
	
	/**
	 * return the next port this agent is to be sent to
	 * 
	 * @return valid port or -1 if not available
	 */
	public int getNextPort(){
		String nextAddress = "";
		String port = "-1";		
		String token;	
		if(nextAddressIndex < addresses.size()){
			nextAddress = addresses.elementAt(nextAddressIndex).toString();	
			Tokenizer tok = new Tokenizer(nextAddress, ":");
			try{
				token = tok.nextToken() + ":" + tok.nextToken() + ":";				
				port = nextAddress.substring(token.length());
			}catch(NoSuchElementException e){
				System.err.println("NoSuchElementException caught in MeetingAgent.getNextPort()");
				e.printStackTrace();
			}
		}
		else{
			System.err.println("nextAddressIndex is trying to access an address " +
					"that is not there in MeetingAgent.getNextPort()");
		}
		return Integer.parseInt(port);
	}
	/**
	 * return the next address and port this agent is to be sent to
	 * 
	 * @return valid address and port or empty string if not available
	 */
	public String getNextAddressPort(){
		String nextAddress = "";
		if(nextAddressIndex < addresses.size()){
			nextAddress = addresses.elementAt(nextAddressIndex).toString();
		}
		else{
			System.err.println("nextAddressIndex is trying to access an address " +
					"that is not there in MeetingAgent.getNextAddressPort()");
		}
		return nextAddress;
	}
	/**************************************************************************
	 * Once this meeting agent has returned to its creator
	 * this method finds the most suitable time, given what 
	 * the contacts on the list chose, and creates a new 
	 * InfoAgent with a message containing the meeting time
	 * 
	 * @return InfoAgent with the meeting time and contacts
	 */
	public InfoAgent convertToInfo(){
		InfoAgent infoAgent = new InfoAgent();
		for(int i=0; i<this.contacts.size(); i++){
			infoAgent.addContact(this.contacts.elementAt(i).toString(), this.addresses.elementAt(i).toString());
		}
		int[] temp = new int[this.startTimes.size()];
		
		//for each time check every contact
		//this is suppose to be the other way around to the toString loops
		for(int j=0; j<this.startTimes.size(); j++){
			for(int i=0; i<this.contacts.size(); i++){
				if(this.available[i][j])
					temp[j]++;						
			}
		}
		
		//now find the most suitable time for a meeting
		int max = 0;
		int maxIndex = 0;
		for(int i=0; i<this.startTimes.size(); i++){
			if(temp[i]> max){
				max = temp[i];
				maxIndex = i;
			}
		}
		infoAgent.setMessage("Meeting is from: " + this.startTimes.elementAt(maxIndex) + 
				" to: " + this.finishTimes.elementAt(maxIndex));
		
		return infoAgent;
	}
	/**************************************************************************
	 *
	 */
	public void printAvailability(){
		for(int i=0; i<this.contacts.size(); i++){
			for(int j=0; j<this.startTimes.size(); j++){
				
				if( this.available[i][j] == true )
					System.out.print("\ti:" + i + ", j:" + j + " value: T");
				else
					System.out.print("\ti:" + i + ", j:" + j + " value: F");
			}
			System.out.println();
		}
	}
}